package com.nycreativity.elecreativity.aop;

import com.alibaba.fastjson.JSONObject;
import com.nycreativity.elecreativity.entity.ResultData;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.List;


/**
 * 参数校验切面
 *
 * @author ttxxi
 * @version : 1.0.0
 */
@Aspect
@Component
@Slf4j
public class ValidateAspect {

    @Pointcut(value = "@annotation(com.nycreativity.elecreativity.aop.ValidateAspect.ParamValidate)")
    public void pointCut() {
    }

    @SuppressWarnings("ConstantConditions")
    @Around(value = "pointCut()")
    public Object paramValidate(ProceedingJoinPoint joinPoint) {
        log.info("进入切面参数校验");
        Object result = null;
        Object[] args = joinPoint.getArgs();
        BindingResult bindingResult = getBindingResultObj(args);
        if (bindingResult != null) {
            log.info("开始处理校验结果");

            ResultData<Object> objectResultData = handleValidateResult(bindingResult);
            if (objectResultData != null) {
                log.info("存在参数校验错误, 将响应带有错误信息的数据");
                HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
                response.setContentType("application/json; charset=utf-8");
                try {
                    response.getWriter().write(JSONObject.toJSONString(objectResultData));
                } catch (IOException e) {
                    log.error("响应过程中出现异常");
                    e.printStackTrace();
                }
            } else {
                log.info("不存在参数校验错误, 切入方法将正常执行...");
                try {
                    result = joinPoint.proceed();
                } catch (Throwable throwable) {
                    log.error("切入方法执行过程中出现异常");
                    throwable.printStackTrace();
                    throw new RuntimeException(throwable.getMessage());
                }
            }
        } else {
            log.info("切入方法不具有BindingResult对象参数, 无效的校验使用, 切入方法将正常执行...");
            try {
                result = joinPoint.proceed();
            } catch (Throwable throwable) {
                log.error("切入方法执行过程中出现异常");
                throw new RuntimeException(throwable.getMessage());
            }
        }
        return result;
    }

    /**
     * 处理数据校验结果, 判断是否有参数校验错误, 若有, 则返回带有错误信息的响应对象
     *
     * @param bindingResult {@link BindingResult }
     * @return 带有错误信息的响应对象
     */
    private ResultData<Object> handleValidateResult(BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            List<ViolationFieldError> fieldErrors = parseFieldErrors(bindingResult);
            log.info("数据校验失败, 传入数据存在问题: {}", fieldErrors);
            return ResultData.FAILED("数据校验失败, 传入数据存在问题", fieldErrors);
        }
        return null;
    }

    /**
     * 获取切入方法中 BindingResult 参数校验结果 对象
     *
     * @param args 切入方法参数数组
     * @return {@link BindingResult}
     */
    private BindingResult getBindingResultObj(Object[] args) {
        for (Object arg : args) {
            if (arg instanceof BindingResult) {
                return (BindingResult) arg;
            }
        }
        return null;
    }

    private List<ViolationFieldError> parseFieldErrors(BindingResult bindingResult) {
        ArrayList<ViolationFieldError> fieldErrors = new ArrayList<>();
        bindingResult.getFieldErrors().forEach(fieldError ->
                fieldErrors.add(new ViolationFieldError(fieldError.getField(), fieldError.getDefaultMessage())));
        return fieldErrors;
    }


    /**
     * 用来标记某个方法是否需要使用切面进行参数校验
     */
    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ParamValidate {

    }


    @Getter
    @Accessors(chain = true)
    @ToString
    @AllArgsConstructor
    private static class ViolationFieldError {

        /**
         * 校验发生错误的字段名
         */
        private final String field;

        /**
         * 错误信息
         */
        private final String errorMessage;
    }
}
